from flask.views import MethodView
from flask import jsonify, request
from base import db


class APIView(MethodView):
    """
    """

    model = None
    schema = None
    nested_views = []
    pk_type = 'int'
    pk_field = 'id'
    path = '/'
    name = ''

    def get_model(self):
        return self.model

    def get_query(self, advanced_conditions=None, q=None, session=None, *args, **kwargs):
        """ 默认获取查询方法, 子类可覆盖本方法实现自定义过滤

            Nested API需要自定义此方法
        """
        if session:
            query = self.get_model().query_class(self.get_model(), session=session)
            print('---using custom session---')
        else:
            query = self.get_model().query
        return query

    def get_object(self, pk=None, raise_404=True, *args, **kwargs):
        query = self.get_query(**kwargs)
        field = getattr(self.get_model(), self.pk_field)
        if raise_404:
            obj = query.filter(field == pk).first_or_404()
        else:
            obj = query.filter(field == pk).first()
        return obj

    def get(self, pk=None, *args, **kwargs):
        if pk is not None:
            return self.retrieve(pk, *args, **kwargs)
        else:
            return self.list(self, *args, **kwargs)

    def list(self, *args, **kwargs):
        query = self.get_query(*args, **kwargs).all()
        total = len(query)
        data = self.schema.dump(query, many=True).data
        return jsonify({'data': data, 'total': total}), 200

    def retrieve(self, pk, *args, **kwargs):
        obj = self.get_object(pk, *args, **kwargs)
        data = self.schema.dump(obj).data
        return jsonify({'data': data}), 200

    def post(self):
        try:
            data = request.get_json()
            # if not data['title'] or not data['content']:
            #     return jsonify({'code': 400})
            post_data = self.schema.load(data).data
            db.session.add(post_data)
            db.session.commit()
            dump_data = self.schema.dump(post_data).data
            return jsonify({'data': dump_data}), 201
        except Exception as e:
            return jsonify({'data': e}), 500

    def put(self, pk, *args, **kwargs):
        payload = request.get_json()

        obj = self.get_object(pk, raise_404=False, **kwargs)
        if obj:
            self.schema.load(payload, session=db.session, instance=obj, partial=True)
            db.session.commit()
            status = 200
        else:
            post = self.model(id=pk, **payload)
            db.session.add(post)
            db.session.commit()
            status = 201
        dump_data = self.schema.dump(obj).data
        return jsonify({'data': dump_data}), status

    def patch(self, pk, *args, **kwargs):
        payload = request.get_json()
        obj = self.get_object(pk, *args, **kwargs)
        self.schema.load(payload, session=db.session, instance=obj, partial=True)
        db.session.commit()
        dump_data = self.schema.dump(obj).data
        return jsonify({'data': dump_data})

    def delete(self, pk, *args, **kwargs):
        obj = self.get_object(pk, *args, **kwargs)
        db.session.delete(obj)
        db.session.commit()
        return jsonify({'id': pk})

    @classmethod
    def register_to(cls, app_or_blueprint):
        for path, path_kwargs in cls.urls():
            app_or_blueprint.add_url_rule(path, **path_kwargs)

    @classmethod
    def urls(cls):
        view_func = cls.as_view(cls.name)
        urls = []
        urls.append((cls.path,
                     {
                      'view_func': view_func,
                      'methods': ["GET"]
                      }))
        urls.append((cls.path,
                     {'view_func': view_func,
                      'methods': ["POST"]
                      }))
        urls.append((cls.path+'/<int:pk>',
                     {'view_func': view_func,
                      'methods': ['GET', 'PUT', 'DELETE', 'PATCH']
                      }))
        for child_ in cls.nested_views:
            if isinstance(child_, type) and issubclass(child_, APIView):
                child = child_
            elif callable(child_) and issubclass(child_(), APIView):
                child = child_()
            else:
                raise ValueError('Illegal view')
            parent_path = '%(parent_path)s/<%(pk_type)s:%(parent_name)s_pk>/%(child_path)s'
            for child_path, child_kwargs in child.urls():
                child_kwargs['endpoint'] = '%s:%s' % (cls.__name__, child_kwargs.get('endpoint'))
                urls.append(
                    (
                        parent_path % {
                            'parent_path': cls.path,
                            'pk_type': cls.pk_type,
                            'parent_name': cls.name,
                            'child_path': child_path if not child_path.startswith('/') else child_path.lstrip('/')
                        },
                        child_kwargs
                    )
                )
        # for url in urls:
        #     print(url)
        return urls